////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleRender.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: sprite particles, big independent polygons
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "Particle.h"
#include "ParticleEmitter.h"
#include "ParticleManager.h"
#include "ObjMan.h"
#include "3dEngine.h"

#include <SpuUtils.h>
#include <IJobManager_JobDelegator.h>

DECLARE_JOB("ComputeVertices", TComputeVerticesJob, CParticleContainer::WriteVerticesIndirectJob );

	typedef TComputeVerticesJob::packet TComputeVerticesJobPacket;
	typedef PROD_CONS_QUEUE_TYPE(TComputeVerticesJob, 256) TComputeVerticesJobQueue;

	// use two queues to prevent stalling when to many jobs are issued
	TComputeVerticesJobQueue& GetProdConsQueueComputeVertices()
	{
		static uint32 nCurQueue = 0;
		static TComputeVerticesJobQueue g_ProdConsQueueComputeVertices1(false);
		static TComputeVerticesJobQueue g_ProdConsQueueComputeVertices2(false);
		TComputeVerticesJobQueue *pCurProdConsQueue[2] = {		&g_ProdConsQueueComputeVertices1, &g_ProdConsQueueComputeVertices2 };
		TComputeVerticesJobQueue &ret = *pCurProdConsQueue[nCurQueue];
		nCurQueue ^= 1; // switch to next queue
		return ret;
	}	


template<class T>
class SaveRestore
{
public:
	SaveRestore(T& var)
		: m_pVar(&var), m_Val(var)
	{}
	SaveRestore(T& var, T const& newval)
		: m_pVar(&var), m_Val(var)
	{
		*m_pVar = newval;
	}
	~SaveRestore()
	{
		*m_pVar = m_Val;
	}
protected:
	T*	m_pVar;
	T		m_Val;
};

struct SParticleRenderData
{
	// Computed params needed for rendering.
	ColorF	cColor;						// Color & alpha.
	float		fSize;						// Radius (possibly clipped).
	float		fDistSq;					// Distance^2 from camera.
	float		fFillPix;					// Approx number of pixels drawn.
};

struct SParticleVertexContext
{
	// Constant data.
	CCamera const*			m_pCamera;
	Matrix34						m_matInvCamera;
	float								m_fProjectH, m_fProjectV;
	UCol								m_TexInfo;							// Pre-computed info vals.
	float								m_fEmitterScale;
	float								m_fMinPixFactor;
	float								m_fMaxPixFactor;
	float								m_fAngleAugment;
	float								m_fFillFactor;
	float								m_fFillMax;							// Max value to clamp particle fill size: screen pixels.
	float								m_fFillFade;
	float								m_fDistFuncCoefs[3];		// Coefficients for alpha(dist^2) function.
	float								m_fMinAlpha;
	Vec2								m_vTexAspect;						// Multipliers for non-square textures (max is always 1).
	float								m_fAnimPosScale;				// Multiplier for texture animation pos.

	SVisEnviron const*	m_pVisEnv;							// Vis env to clip against, if needed.

	// Modified data.
	float								m_fPixelsProcessed;
	float								m_fPixelsRendered;
	
	// Connected-particle info.
	int									m_nFirstVert;
	int									m_nPrevSequence;

	SParticleVertexContext( const CCamera* pCam, float fMaxContainerPixels, const CParticleContainer* pContainer )
	:	m_pCamera(pCam)
	{
		Init( fMaxContainerPixels, pContainer );
	}

	void Init( float fMaxContainerPixels, const CParticleContainer* pContainer );

	inline float DistFunc( float fDist ) const
	{
		return clamp_tpl( m_fDistFuncCoefs[0] + m_fDistFuncCoefs[1]*fDist + m_fDistFuncCoefs[2]*fDist*fDist, 0.f, 1.f	);
	}
};

//////////////////////////////////////////////////////////////////////////
bool CParticle::RenderGeometry( SRendParams& RenParamsShared, SParticleVertexContext& Context ) const
{
	// Render 3d object
	if (!m_EmitGeom.m_pStatObj)
		return false;

	const ResourceParticleParams& params = GetParams();

	SParticleRenderData RenderData;
	ComputeRenderData( RenderData, Context, GetRelativeAge(), m_EmitGeom.m_pStatObj->GetRadius() ); 
	if (RenderData.fSize == 0.f)
		return false;
	RenderData.cColor.a *= RenParamsShared.fAlpha * params.fAlpha.GetMaxValue();
	if (RenderData.cColor.a < Context.m_fMinAlpha)
		return false;
	if (Get3DEngine()->IsRenderingIntoShadowMap())
	{
		// Shadow alpha (and color) not supported, scale size instead.
		RenderData.fSize *= RenderData.cColor.a;
		RenderData.cColor = Col_White;
	}

	// Get matrix.
	PivotLocation locPart;
	GetRenderLocation(locPart);
	locPart.s = RenderData.fSize;
	Matrix34 matPart(locPart);

#ifdef SHARED_GEOM
	if (RenParamsShared.pInstInfo && m_EmitGeom.m_pStatObj == params.pStatObj)
	{
		// Add shared geometry instance.
		SInstanceInfo Inst = { 
			matPart,
			// matPartPrev,		// Add this when it's supported.
			RenParamsShared.AmbientColor * RenderData.cColor
		};
		RenParamsShared.pInstInfo->arrMats.push_back(Inst);
	}
	else
#endif
	{
		// Render separate draw call.
		SaveRestore<SInstancingInfo*> SaveInst(RenParamsShared.pInstInfo, 0);
		SaveRestore<ColorF> SaveColor(RenParamsShared.AmbientColor);
		SaveRestore<float> SaveAlpha(RenParamsShared.fAlpha);

		// Apply particle color to RenParams.Ambient, tho it's not quite the same thing.
		RenParamsShared.AmbientColor.r *= RenderData.cColor.r;
		RenParamsShared.AmbientColor.g *= RenderData.cColor.g;
		RenParamsShared.AmbientColor.b *= RenderData.cColor.b;
		RenParamsShared.fAlpha = RenderData.cColor.a;

		RenParamsShared.fDistance = max((Context.m_pCamera->GetPosition() - locPart.t).GetLengthFast() - locPart.s, 0.f) * m_EmitGeom.m_pStatObj->GetRadius();
		RenParamsShared.fDistance *= m_fZoomFactor;

		RenParamsShared.pMatrix = &matPart;            
		RenParamsShared.pInstance = &non_const(*this);
		m_EmitGeom.m_pStatObj->Render(RenParamsShared);
	}

	return true;
}

void CParticleContainer::RenderGeometry( const SRendParams& RenParams, const CCamera& cam )
{
  FUNCTION_PROFILER_CONTAINER(this);

	if (m_Particles.empty())
		return;

	const ResourceParticleParams& params = GetParams();
	SRendParams RenParamsGeom = RenParams;
  RenParamsGeom.dwFObjFlags |= FOB_TRANS_MASK;
	RenParamsGeom.pMaterial = params.pMaterial;
	if (params.bDrawNear)
	{
		RenParamsGeom.dwFObjFlags |= FOB_NEAREST;
		//RenParamsGeom.nRenderList = EFSLIST_POSTPROCESS;
	}
	RenParamsGeom.fCustomSortOffset = -m_fSortBias;

	// Trigger transparency rendering if any particles can be transparent.
	if (!Get3DEngine()->IsRenderingIntoShadowMap() && params.fAlpha.GetMinValue() < 1.f)
		RenParamsGeom.fAlpha *= 0.999f;

	float fEmissive = params.fEmissiveLighting;
	if (GetRenderer()->EF_Query( EFQ_HDRModeEnabled ))
		fEmissive *= powf( Get3DEngine()->GetHDRDynamicMultiplier(), params.fEmissiveHDRDynamic );
	RenParamsGeom.AmbientColor *= params.fDiffuseLighting + fEmissive;

#ifdef SHARED_GEOM
	if (m_pCVars->e_ParticlesDebug & AlphaBit('g'))
	{
		RenParamsGeom.pInstInfo = GetMain().GetInstancingInfo();
		RenParamsGeom.pInstInfo->arrMats.resize(0);
		RenParamsGeom.pInstInfo->aabb = m_bbWorld;
	}
	else
	{
		RenParamsGeom.pInstInfo = 0;
	}
#endif

	// Set up shared and unique geom rendering.
	SParticleVertexContext Context( &cam, 0.f, this );

	m_Counts.EmittersRendered += 1.f;

	for (TParticleList::const_traverser pPart(m_Particles); pPart; ++pPart)
	{
		m_Counts.ParticlesRendered += pPart->RenderGeometry( RenParamsGeom, Context );
	}

#ifdef SHARED_GEOM
	// Render shared geom.
	if (RenParamsGeom.pInstInfo && RenParamsGeom.pInstInfo->arrMats.size())
	{
		static Matrix34 mxIdentity(IDENTITY);
		RenParamsGeom.pMatrix = &mxIdentity;
		params.pStatObj->Render(RenParamsGeom);
	}
#endif
}

void CParticleContainer::RenderDecals()
{
  FUNCTION_PROFILER_CONTAINER(this);

	assert(GetEnvironmentFlags() & REN_DECAL);

	ResourceParticleParams const& params = GetParams();

	SDeferrredDecal decal;
	decal.pMaterial = params.pMaterial;
	decal.nSortOrder = max(params.nDrawLast, (int8)0);
	decal.nFlags = 0;
	decal.fGrowAlphaRef = 0.f;

	// Matrix to re-orient particle from Y to Z forward, and scale Z by thickness.
	Matrix33 mxAdjust( 1, 0, 0, 0, 0, params.fThickness, 0, -1, 0);

	for (TParticleList::const_traverser pPart(m_Particles); pPart; ++pPart)
	{
		PivotLocation loc;
		pPart->GetRenderLocation(loc);
		decal.projMatrix = Matrix34(loc) * mxAdjust;
		decal.fAlpha = pPart->GetAlpha();

		GetRenderer()->EF_AddDeferrredDecal(decal);
	}
}

void CParticle::AddLight() const
{
	ParticleParams const& params = GetParams();
	CCamera const& cam = GetRenderer()->GetCamera();

	float fRelativeAge = GetRelativeAge();
	const float fFillLightIntensity = params.LightSource.fIntensity.GetValueFromMod(m_BaseMods.LightSourceIntensity, fRelativeAge);
	const float fFillLightRadius = params.LightSource.fRadius.GetValueFromMod(m_BaseMods.LightSourceRadius, fRelativeAge);
	if (fFillLightIntensity * fFillLightRadius > 0.001f
	&& m_Loc.t.GetSquaredDistance(cam.GetPosition()) < sqr(fFillLightRadius*GetFloatCVar(e_ParticlesLightsViewDistRatio))
	&& cam.IsSphereVisible_F(Sphere(m_Loc.t, fFillLightRadius)))
	{
		// Deferred light.
	  CDLight dl;
		dl.SetPosition( m_Loc.t );
		dl.m_fRadius = fFillLightRadius;
		dl.m_Color = params.cColor.GetValueFromBase(m_BaseColor, fRelativeAge) * Color3F(fFillLightIntensity);
		// AntonK: remained from the fill lights code. Is it needed for deferred lights?
		//dl.m_Color *= powf( Get3DEngine()->GetHDRDynamicMultiplierInline(), params.LightSource.fHDRDynamic.GetValueFromMod(m_BaseMods.LightHDRDynamic, m_fRelativeAge) );
		dl.m_fHDRDynamic = params.LightSource.fHDRDynamic;

		dl.m_Flags |= DLF_DEFERRED_LIGHT;
		Get3DEngine()->SetupLightScissors(&dl);

		GetRenderer()->EF_AddDeferredLight(dl, 1.f);
		Get3DEngine()->m_nDeferredLightsNum++;
	}
}

void CParticleContainer::RenderLights()
{
  FUNCTION_PROFILER_CONTAINER(this);

	assert(GetEnvironmentFlags() & REN_LIGHTS);

	for (TParticleList::const_traverser pPart(m_Particles); pPart; ++pPart)
	{
		pPart->AddLight();
	}
}

//////////////////////////////////////////////////////////////////////////
ILINE void ConvertColor( UCol& col, ColorF const& color )
{
	col.a = float_to_ufrac8(color.a);
	col.r = float_to_ufrac8(color.r);
	col.g = float_to_ufrac8(color.g);
	col.b = float_to_ufrac8(color.b);
}

//
// Vertex manipulation functions.
//

static ILINE void RotateAxes( SVertexParticle& Vert, const Vec2& vY )
{
	// Rotate within axis plane to align Y axis.
	Vec3 vXAxis = Vert.xaxis * vY.y - Vert.yaxis * vY.x;
	Vec3 vYAxis = Vert.xaxis * vY.x + Vert.yaxis * vY.y;
	Vert.xaxis = vXAxis;
	Vert.yaxis = vYAxis;
}

SPU_NO_INLINE static void SetVertexLocation( SVertexParticle& Vert, const QuatTS& Loc, const SParticleVertexContext& Context, const ParticleParams& params )
{
	Vert.xyz = Loc.t;

	// Set X and Y axes; Y reversed to match texture orientation.
	if (params.eFacing != ParticleFacing_Camera)
	{
		// 3D oriented particles.
		Vert.xaxis = Loc.q.GetColumn0();
		Vert.yaxis = -Loc.q.GetColumn2();
	}
	else
	{
		// Set axes to camera plane, aligning YAxis with YDir.
		Vec3 vToCam = (Context.m_pCamera->GetPosition() - Vert.xyz).GetNormalized();
		Vert.xaxis = Vec3(-vToCam.y, vToCam.x+FLT_EPSILON, 0).GetNormalizedFast();
		Vert.yaxis = Vert.xaxis ^ vToCam;

		if (params.bOrientToVelocity)
		{
			Vec3 vYDir = -Loc.q.GetColumn2();
			RotateAxes(Vert, Vec2(Vert.xaxis * vYDir, Vert.yaxis * vYDir).GetNormalized());

			if (Context.m_vTexAspect.x < 1.f)
			{
				// Allow Y axis to project into screen.
				float fProj = (vYDir ^ vToCam).GetLengthFast();
				if (fProj < Context.m_vTexAspect.x)
					Vert.yaxis = vYDir * fProj + Vert.yaxis * Context.m_vTexAspect.x;
				else
					Vert.yaxis = vYDir;
				Vert.yaxis.NormalizeFast();
			}
		}
	}
}

ILINE void ExpandVerticesX( SVertexParticle aVerts[2] )
{
	// Expand in transverse axis.
	aVerts[1] = aVerts[0];
	aVerts[0].st.x = 0;
	aVerts[1].st.x = 255;
}

SPU_NO_INLINE void CParticle::ComputeRenderData( SParticleRenderData& RenderData, SParticleVertexContext& Context, float fRelativeAge, float fObjectSize ) const
{
	const ResourceParticleParams& params = GetParams();

	// Color and alpha.
	new (&RenderData.cColor) ColorF
	(
		params.cColor.GetValueFromBase(m_BaseColor, fRelativeAge),
		params.fAlpha.GetValueFromBase(m_BaseAlpha, fRelativeAge)
	);

	Vec3 vDist = m_Loc.t - Context.m_pCamera->GetPosition();
	RenderData.fDistSq = vDist.GetLengthSquared();

	RenderData.fSize = m_Loc.s;

	IF (Context.m_fAngleAugment != 0, false)
	{
		float fDist = sqrt_fast_tpl(RenderData.fDistSq);
		RenderData.fSize += fDist * Context.m_fAngleAugment;
	}

	// Shrink particle when approaching visible limits.
	IF (Context.m_pVisEnv, false)
	{
		// Particles clipped against visibility areas. Shrink only.
		float fVisSize = RenderData.fSize * GetBaseRadius();
		Sphere sphere(m_Loc.t, fVisSize);
		Vec3 vNormal;
		if (params.bSoftParticle)
			vNormal.zero();
		else if (params.eFacing == ParticleFacing_Camera)
			vNormal = (Context.m_pCamera->GetPosition() - m_Loc.t).GetNormalized();
		else
			vNormal = GetNormal();

		if (Context.m_pVisEnv->ClipVisAreas(sphere, vNormal))
		{
			GetContainer().GetCounts().ParticlesClip += 1.f;
			if (sphere.radius <= 0.f)
				RenderData.fSize = sphere.radius = 0.f;
			else
				RenderData.fSize *= sphere.radius / fVisSize;
		}
	}

	// Area of particle in square radians.
	RenderData.fFillPix = div_min(square(RenderData.fSize * fObjectSize) * Context.m_fFillFactor, RenderData.fDistSq, Context.m_fFillMax);

	// Fade near size limits.
	RenderData.cColor.a *= 
		clamp_tpl(RenderData.fFillPix * Context.m_fMinPixFactor - 1.f, 0.f, 1.f) * 
		clamp_tpl(RenderData.fFillPix * Context.m_fMaxPixFactor + 2.f, 0.f, 1.f) *
		Context.DistFunc(RenderData.fDistSq);
}

//////////////////////////////////////////////////////////////////////////
int CParticle::SetVertices( SVertexParticle aVerts[], SParticleVertexContext& Context ) const
{
	const ResourceParticleParams& params = GetParams();
	float fRelativeAge = GetRelativeAge();

	// Compute stretch early (needed for culling)
	Vec3 vStretch;
	float fStretchLen = 0.f;
	if (params.fStretch && params.eFacing != ParticleFacing_Velocity)
	{
		// Disallow stretching further back than starting position.
		float fStretch = params.fStretch.GetValueFromMod(m_BaseMods.Stretch, fRelativeAge);
		if (fStretch * (1.f - params.fStretch.fOffsetRatio) > m_fAge)
			fStretch = m_fAge / (1.f - params.fStretch.fOffsetRatio);
		vStretch = m_Vel.vLin * fStretch;
		fStretchLen = vStretch.GetLengthFast();
	}

	if (GetCVars()->e_ParticlesCullAgainstViewFrustum)
	{
		// Accurately cull against view frustum.
		Vec3 vPosCam = Context.m_matInvCamera * m_Loc.t;
		float fRadius = m_Loc.s * 1.415f + fStretchLen;
		if (max( abs(vPosCam.x) - fRadius*Context.m_fProjectH, abs(vPosCam.z) - fRadius*Context.m_fProjectV ) > vPosCam.y)
			return 0;
	}

	// Compute main render data.
	SParticleRenderData RenderData;
	ComputeRenderData( RenderData, Context, fRelativeAge, 1.f );

	// Track scr fill.
	RenderData.fFillPix *= params.fFillRateCost;
	Context.m_fPixelsProcessed += RenderData.fFillPix;

	// Cull oldest particles to enforce max screen fill.
	float fAdjust = min(2.f - Context.m_fPixelsProcessed * Context.m_fFillFade, 1.f);
	RenderData.cColor.a *= fAdjust;

	if (RenderData.cColor.a < Context.m_fMinAlpha)
		return 0;

	SVertexParticle& BaseVert = aVerts[0];

	// 8-bit color.
	ConvertColor( BaseVert.color, RenderData.cColor );

	SetVertexLocation( BaseVert, m_Loc, Context, params );

	if (m_fAngle != 0.f)
	{
		// Apply planar rotation.
		Vec2 vDir;
		sincos_tpl( m_fAngle, &vDir.x, &vDir.y );
		RotateAxes( BaseVert, vDir );
	}

	// Scale axes by particle size.
	BaseVert.xaxis *= RenderData.fSize * Context.m_vTexAspect.x;
	BaseVert.yaxis *= RenderData.fSize * Context.m_vTexAspect.y;

	// Texture info.
	BaseVert.st = Context.m_TexInfo;

	if (params.Connection)
	{
		// Fill in basic texture information here. Axis alignment and vertex doubling is done by caller.
		float fTexY = params.Connection.eTextureMapping == TextureMapping_PerStream ? fRelativeAge : float(m_nTileVariant) - min(m_fAge, 0.f) / m_fEndAge * params.fCount.GetMaxValue();
		fTexY = fabs(fmod(fTexY * params.Connection.fTextureFrequency, 2.f) - 1.f);
		BaseVert.st.y = float_to_ufrac8(fTexY);

#if PARTICLE_MOTION_BLUR
		// Initialize MB values to prevent FPE
		BaseVert.prevPos = BaseVert.xyz;
		BaseVert.prevXaxis = BaseVert.xaxis;
		BaseVert.prevYaxis = BaseVert.yaxis;
#endif

		return 2;
	}

	float fFramePos = (float)m_nTileVariant;
	if (params.TextureTiling.nAnimFramesCount > 1)
	{
		// Select tile based on particle age.
		float fAnimPos;
		if (params.TextureTiling.fAnimFramerate > 0.f)
		{
			fAnimPos = m_fAge * params.TextureTiling.fAnimFramerate / params.TextureTiling.nAnimFramesCount;
			if (params.TextureTiling.eAnimCycle == AnimationCycle_Loop)
				fAnimPos = fmod(fAnimPos, 1.f);
			else if (params.TextureTiling.eAnimCycle == AnimationCycle_Mirror)
				fAnimPos = 1.f - abs(fmod(fAnimPos, 2.f) - 1.f);
			else
				fAnimPos = min(fAnimPos, 1.f);
		}
		else
			fAnimPos = fRelativeAge;

		fFramePos += fAnimPos;
	}

	BaseVert.st.z += check_cast<uint8>( int(fFramePos * Context.m_fAnimPosScale) );

	if (m_aPosHistory)
	{
		return FillTailVertBuffer( aVerts, Context );
	}

	// Apply stretch.
	if (fStretchLen > FLT_MIN)
	{
		BaseVert.xaxis += vStretch * ((BaseVert.xaxis * vStretch) / (RenderData.fSize * Context.m_vTexAspect.x * fStretchLen));
		BaseVert.yaxis += vStretch * ((BaseVert.yaxis * vStretch) / (RenderData.fSize * Context.m_vTexAspect.y * fStretchLen));
		BaseVert.xyz += vStretch * params.fStretch.fOffsetRatio;
	}

	Context.m_fPixelsRendered += RenderData.fFillPix;

#if PARTICLE_MOTION_BLUR
	IF_LIKELY (m_Prev.bValid)
	{
		BaseVert.prevPos = m_Prev.vPos;
		BaseVert.prevXaxis = m_Prev.vXAxis;
		BaseVert.prevYaxis = m_Prev.vYAxis;
	}
	else
	{
		const float fFrame = GetParticleTimer()->GetFrameTime();
		BaseVert.prevPos = BaseVert.xyz - m_Vel.vLin * fFrame;
		BaseVert.prevXaxis = BaseVert.xaxis;
		BaseVert.prevYaxis = BaseVert.yaxis;
	}
#endif

	return 1;
}

int CParticle::FillTailVertBuffer( SVertexParticle aTailVerts[], SParticleVertexContext const& context ) const
{
	const ResourceParticleParams& params = GetParams();

	const SVertexParticle* pVertStart = aTailVerts;

	// First segment is front of particle, expanded forward
	ExpandVerticesX( aTailVerts );
	aTailVerts += 2;

	float fRelativeAge = GetRelativeAge();
	float fTailLength = min(params.fTailLength.GetValueFromMod(m_BaseMods.TailLength, fRelativeAge), m_fAge);
	if (fTailLength > 0)
	{
		// Fill with past positions.
		int nPos = GetContainer().GetHistorySteps()-1;
		for (; nPos >= 0; nPos--)
		{
			if (m_aPosHistory[nPos].IsUsed())
				break;
		}

		// Center point. Estimate tex coord from number of active segments.
		const float fTexEnd = div_min(m_Loc.s, m_Vel.vLin.GetLengthFast() * fTailLength + m_Loc.s + m_Loc.s, 0.5f);
		const float fTexTimeGradient = (1.f - fTexEnd - fTexEnd) / fTailLength;

		*aTailVerts = *pVertStart;
		aTailVerts->xyz = m_Loc.t;
		aTailVerts->st.y = float_to_ufrac8(fTexEnd);
		aTailVerts->st.w = 0;  // Disable y-axis expansion.
		ExpandVerticesX( aTailVerts );
		aTailVerts += 2;

		for (; nPos >= 0; nPos--)
		{
			QuatTS qtLoc = m_aPosHistory[nPos].Loc;

			float fAgeDelta = m_fAge - m_aPosHistory[nPos].fAge;
			if (fAgeDelta > fTailLength && nPos < GetContainer().GetHistorySteps()-1)
			{
				// Interpolate oldest 2 locations if necessary.
				if (m_fAge - fTailLength > m_aPosHistory[nPos+1].fAge)
					break;
				float fT = (m_fAge - fTailLength - m_aPosHistory[nPos].fAge) / (m_aPosHistory[nPos+1].fAge - m_aPosHistory[nPos].fAge);
				if (fT > 0.f)
					qtLoc.SetNLerp( qtLoc, m_aPosHistory[nPos+1].Loc, fT );
				fAgeDelta = fTailLength;
			}

			*aTailVerts = *pVertStart;
			SetVertexLocation( *aTailVerts, qtLoc, context, params );

			aTailVerts->xaxis *= qtLoc.s * context.m_vTexAspect.x;
			aTailVerts->yaxis *= qtLoc.s * context.m_vTexAspect.y;

			float fTexY = fTexEnd + fTexTimeGradient * fAgeDelta;
			aTailVerts->st.y = float_to_ufrac8(fTexY);
			aTailVerts->st.w = 0;  // Disable y-axis expansion.
			ExpandVerticesX( aTailVerts );
			aTailVerts += 2;
		}
	}

	// Half particle at tail end. Set st.w to expand on y axis.
	*aTailVerts = aTailVerts[-2];
	aTailVerts->st.y = 255;
	aTailVerts->st.w = 255;
	ExpandVerticesX( aTailVerts );
	aTailVerts += 2;

	return check_cast<int>(aTailVerts - pVertStart);
}

SPU_NO_INLINE static void SetSegment( SVertexParticle aVerts[2], ParticleParams const& params, Vec3 const& vDir )
{
	if (params.eFacing == ParticleFacing_Camera)
	{
		Vec2 vProj(aVerts->xaxis * vDir, aVerts->yaxis * vDir);
		float fProjLen2 = vProj.GetLength2();
		if (fProjLen2 > FLT_MIN)
		{
			vProj *= isqrt_fast_tpl(fProjLen2);
			RotateAxes(*aVerts, vProj);
		}
	}
	else
	{
		float fDirLen2 = vDir.len2();
		if (fDirLen2 > FLT_MIN)
		{
			Quat qAlign = Quat::CreateRotationV0V1(aVerts->yaxis.GetNormalized(), vDir * isqrt_fast_tpl(fDirLen2));
			aVerts->yaxis = qAlign * aVerts->yaxis;
			aVerts->xaxis = qAlign * aVerts->xaxis;
		}
	}

	ExpandVerticesX( aVerts );
}

SPU_NO_INLINE int ConnectVertices( FixedDynArray<SVertexParticle>& aVertices, int& nFirstVert, ParticleParams const& params )
{
	int nPolyVerts = aVertices.size() - nFirstVert;
	if (nPolyVerts < 4)
	{
		// Isolated particle, no drawing.
		aVertices.resize(nFirstVert);
		return 0;
	}

	SVertexParticle* pVert = &aVertices[nFirstVert];
	SVertexParticle* pVertEnd = aVertices.end()-2;

	// First segment.
	SetSegment( pVert, params, pVert[2].xyz - pVert[0].xyz );

	// Mid segments.
	for (pVert += 2; pVert < pVertEnd; pVert += 2)
		SetSegment( pVert, params, pVert[2].xyz - pVert[-2].xyz );

	// Last segment.
	SetSegment( pVert, params, pVert[0].xyz - pVert[-2].xyz );

	nFirstVert = aVertices.size();
	return nPolyVerts;
}

void CParticleContainer::WriteVerticesIndirectImpl( SParticleVertexContext& vcontext, SRenderVertices& alloc )
{	 
	FUNCTION_PROFILER_CONTAINER(this);
	
	// For SPU, redirect vertex writing to local buffer. For other platforms, alias the allocated verts.







	FixedDynArray<SVertexParticle>& aVertices = alloc.aVertices;

	
	// Create and save vertices.
	ResourceParticleParams const& params = GetParams();
	int nMaxParticleVerts = params.GetMaxVertexCount();

	bool bTail = GetHistorySteps() > 0;

	for (TParticleList::const_traverser pPart(m_Particles); pPart; ++pPart)
	{
		IF (params.Connection && (pPart->GetEmitterSequence() != vcontext.m_nPrevSequence || aVertices.available() < nMaxParticleVerts), false)
		{
			// End connected sequence.
			int nPolyVerts = ConnectVertices( aVertices, vcontext.m_nFirstVert, params );
			if (nPolyVerts)
				// Store indices/vertex count.
				alloc.aIndices.push_back( check_cast<uint16>(nPolyVerts) );
			vcontext.m_nPrevSequence = pPart->GetEmitterSequence();
		}












		SVertexParticle* pBaseVert = aVertices.end();
		int nVerts = pPart->SetVertices( pBaseVert, vcontext );
		assert(nVerts <= nMaxParticleVerts);

		if (nVerts)
		{
			aVertices.grow_raw(nVerts);

#if PARTICLE_MOTION_BLUR
			pPart->UpdatePrevState(*pBaseVert);
#endif
			m_Counts.ParticlesRendered += 1.f;
			assert(nVerts <= nMaxParticleVerts);
			if (bTail)
				alloc.aIndices.push_back(check_cast<uint16>(nVerts));
			else
				assert(nVerts == nMaxParticleVerts);
		}
	}

	if (params.Connection)
	{
		// End last connected sequence.
		int nPolyVerts = ConnectVertices( aVertices, vcontext.m_nFirstVert, params );
		if (nPolyVerts)
			// Store indices/vertex count.
			alloc.aIndices.push_back( check_cast<uint16>(nPolyVerts) );
	}

	alloc.fPixels += vcontext.m_fPixelsProcessed;

	m_Counts.PixelsProcessed += vcontext.m_fPixelsProcessed;
	m_Counts.PixelsRendered += vcontext.m_fPixelsRendered;
	m_Counts.EmittersRendered += 1.f;





}

void CParticleContainer::ComputeVertices( const CCamera& cam, IAllocRender& alloc )
{
	// Update, if not yet done.
	assert(GetEnvironmentFlags() & REN_SPRITE);
	assert(!m_pParams->pStatObj);

	// When using the job system, we have already ensured that all necessary UpdateParticle calls happened.
	if (!m_Particles.empty())
	{
		FUNCTION_PROFILER_CONTAINER(this);

		ResourceParticleParams const& params = *m_pParams;
		if (params.fTexAspect == 0.f)
			non_const(*m_pParams).UpdateTextureAspect();

		StartComputeVerticesAsJob( &cam, alloc );
	}
} 

float CParticleContainer::GetDistSquared( const Vec3& vPos ) const
{
	static const float fInnerDistRatio = 0.25f;			// Amount to scale distance inside emitter bounds.

	AABB const& bb = m_bbWorld;
	Vec3 vDist = vPos - bb.GetCenter();
	float fDist2 = vDist.GetLengthSquared();

	if (fInnerDistRatio != 1.f)
	{
		if (bb.GetVolume())
		{
			// For smoother results, treat the bounding volume as an ellipsoid rather than a box.
			Vec3 vSize = bb.GetSize() * 0.5f;
			Vec3 vUni(vDist.x/vSize.x, vDist.y/vSize.y, vDist.z/vSize.z);
			float fUni = vUni.GetLength();
			if (fUni <= 1.f)
				fDist2 *= square(fInnerDistRatio);
			else
				fDist2 *= square((fUni - 1.f + fInnerDistRatio) / fUni);
		}
	}

	return fDist2;
}

float CParticleContainer::GetApproxParticleArea () const
{
	ResourceParticleParams const& params = GetParams();
	return params.fCount.GetMaxValue() * params.GetMaxParticleSize() * 4.f * params.fFillRateCost;
}

SPU_NO_INLINE void SParticleVertexContext::Init( float fMaxContainerPixels, const CParticleContainer* pContainer )
{
	ResourceParticleParams const& params = pContainer->GetParams();

	CParticleEmitter const& emitterMain = pContainer->GetMain();
	m_fEmitterScale = emitterMain.GetParticleScale();
	const float fFOVX = m_pCamera->GetHorizontalFov(), fFOVY = m_pCamera->GetFov();
	const float fAngularRes = m_pCamera->GetAngularResolution();
	m_fMaxPixFactor = params.nEnvFlags & REN_SPRITE ?
		-2.f * params.fFillRateCost / sqr( max(emitterMain.GetCVars()->e_ParticlesMaxDrawScreen * fAngularRes, 1.f) )
		: 0.f;
	m_fFillMax = (float) m_pCamera->GetViewSurfaceX() * m_pCamera->GetViewSurfaceZ();

	m_vTexAspect.x = m_vTexAspect.y = 1.f;
	if (!(params.nEnvFlags & REN_GEOMETRY))
	{
		if (params.fTexAspect < 1.f)
			m_vTexAspect.x = params.fTexAspect;
		else
			m_vTexAspect.y = 1.f / params.fTexAspect;
	}

	m_fFillFactor = 4.f * m_vTexAspect.x * m_vTexAspect.y * m_fFillMax / (fFOVX*fFOVY);

	m_fFillFade = fMaxContainerPixels > 0.f ? 2.f / fMaxContainerPixels : 0.f;

	m_fMinPixFactor = sqr( emitterMain.GetViewDistRatioFloat() * params.fViewDistanceAdjust / (max(emitterMain.GetCVars()->e_ParticlesMinDrawPixels, 0.125f)) );

	m_fDistFuncCoefs[0] = 1.f;
	m_fDistFuncCoefs[1] = m_fDistFuncCoefs[2] = 0.f;

	m_fMinAlpha = params.Connection ? 0.f : emitterMain.GetCVars()->e_ParticlesMinDrawAlpha;

	// Size = Pix/2 * Dist / AngularRes
	const float fMinPixels = max(+params.fMinPixels, float(params.Connection) * 0.125f);
	m_fAngleAugment = fMinPixels * 0.5f / fAngularRes;

	if (emitterMain.GetCVars()->e_ParticlesCullAgainstViewFrustum)
	{
		m_matInvCamera = m_pCamera->GetViewMatrix();
		Vec3 vFrust = m_pCamera->GetEdgeP();
		float fInvX = abs(1.f / vFrust.x),
					fInvZ = abs(1.f / vFrust.z);
		
		non_const(m_matInvCamera.GetRow4(0)) *= vFrust.y * fInvX;
		non_const(m_matInvCamera.GetRow4(2)) *= vFrust.y * fInvZ;

		m_fProjectH = sqrt_tpl(sqr(vFrust.x) + sqr(vFrust.y)) * fInvX;
		m_fProjectV = sqrt_tpl(sqr(vFrust.z) + sqr(vFrust.y)) * fInvZ;
	}

	// Use 1/FOV.vert as zoom factor.
	float fInvZoom = fFOVY;
	if (params.fCameraMaxDistance > 0.f)
	{
		float fZoom = 1.f / fInvZoom;
		float fNear = sqr(params.fCameraMinDistance * fZoom);
		float fFar = sqr(params.fCameraMaxDistance * fZoom);
		float fBorder = (fFar-fNear) * 0.1f;
		if (fNear == 0.f)
			// No border on near side.
			fNear = -fBorder;

		/*	f(x) = (1 - ((x-C)/R)^2) / s		; C = (N+F)/2, R = (F-N)/2
					= (-NF + (N+F) x - x^2) / s
				f(N+B) = 1 
				s = B(F-N-B)
		*/
		float fS = 1.f / (fBorder*(fFar - fNear - fBorder));
		m_fDistFuncCoefs[0] = -fNear*fFar*fS;
		m_fDistFuncCoefs[1] = (fNear+fFar)*fS;
		m_fDistFuncCoefs[2] = -fS;
	}
	else if (params.fCameraMinDistance > 0.f)
	{
		m_fDistFuncCoefs[1] = 4.f * fInvZoom / params.fCameraMinDistance; 
		m_fDistFuncCoefs[0] = -4.f;
	}
 
	// Pre-compute tex coord info. w indicates y-axis expansion factor, default = 1.
	m_TexInfo.dcolor = 0;
	m_TexInfo.w = params.HasVariableVertexCount() ? 0 : 255;

	if (params.bOctagonalShape)
	{
		// Do not use octagonal shapes for small particles
		static const float fOCTAGONAL_ANGLE_THRESHOLD = 0.03f;
		const float fDistSqr = pContainer->GetBounds().GetDistanceSqr(m_pCamera->GetPosition());
		const float fMaxSize = params.fSize.GetMaxValue() * m_fEmitterScale;
		if (square(fMaxSize * params.fFillRateCost) > square(fOCTAGONAL_ANGLE_THRESHOLD) * fDistSqr * fInvZoom)
		{
			// Flag for octagonal expansion stored in X coord.
			m_TexInfo.x = 1;
			m_fFillFactor *= 0.8284f;
		}
	}

	// Scale animation position to uint8, reducing slightly to avoid hitting 1.
	m_fAnimPosScale = 254.99f / (float)max((int)params.TextureTiling.nVariantCount, 1);

	m_pVisEnv = &emitterMain.GetVisEnviron();
	if ((pContainer->GetCVars()->e_ParticlesDebug & AlphaBit('c')) || !m_pVisEnv->NeedVisAreaClip())
		m_pVisEnv = 0;

	m_fPixelsProcessed = m_fPixelsRendered = 0.f;
	m_nFirstVert = 0;
	m_nPrevSequence = -1;
}

SPU_ENTRY(ComputeVertices)
void CParticleContainer::WriteVerticesIndirectJob( CCamera const* pCam, Array<SVertexParticle> aVertices, Array<uint16> aVertCounts, float fMaxPixels, CREParticle* pRE )
{




	SParticleVertexContext vcontext( pCam, fMaxPixels, this );
	SRenderVertices RenderVerts;
	RenderVerts.aVertices.set(aVertices);
	RenderVerts.aIndices.set(aVertCounts);

	WriteVerticesIndirectImpl( vcontext, RenderVerts );

	pRE->SetVertices( RenderVerts.aVertices(), RenderVerts.aIndices(), RenderVerts.fPixels );
}

void CParticleContainer::StartComputeVerticesAsJob( CCamera const* pCam, IAllocRender &alloc )
{
	// need to move memory allocation away from SPU
	int nParticles = m_Particles.size();
	int nMaxVertexCount = GetParams().GetMaxVertexCount();
	int nAllocVerts = nParticles * nMaxVertexCount;
	int nAllocVertCounts = GetParams().HasVariableVertexCount() ? nParticles : 0;

	alloc.Alloc( nAllocVerts, nAllocVertCounts );

	TComputeVerticesJobPacket packet( pCam,
		ArrayT(alloc.aVertices.begin(), alloc.aVertices.capacity()), 
		ArrayT(alloc.aIndices.begin(), alloc.aIndices.capacity()), alloc.fMaxPixels, alloc.RenderElement() );

	packet.RegisterJobState(alloc.RenderElement()->GetSPUState());
	packet.SetClassInstance(this);
	ScopedSwitchToGlobalHeap useGlobalHeap;	// want to avoid doing this above the other allocs as they alloc and free over a level
	GetProdConsQueueComputeVertices().AddPacket( packet, JobManager::SPUBackend::eCM_8 );
}

void SyncComputeVerticesJobQueues()
{
	// call two times(we have two queues)
	GetProdConsQueueComputeVertices().WaitFinished();
	GetProdConsQueueComputeVertices().WaitFinished();
}

